﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace Demo.StreetView
{
    public class PanoramaViewer : Viewport3D
    {
        public double FieldOfView
        {
            get
            {
                return (double)GetValue(FieldOfViewProperty);
            }
            set
            {
                SetValue(FieldOfViewProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for FieldOfView.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FieldOfViewProperty =
            DependencyProperty.Register("FieldOfView",
                typeof(double),
                typeof(PanoramaViewer),
                new PropertyMetadata(
                    (double)0,
                    OnFieldOfViewChanged));

        internal static void OnFieldOfViewChanged(Object sender, DependencyPropertyChangedEventArgs e)
        {
            PanoramaViewer Viewer = (PanoramaViewer)sender;
            PerspectiveCamera Camera = Viewer.Camera as PerspectiveCamera;
            Camera.FieldOfView = Viewer.FieldOfView;
        }

        public ImageSource PanoramaImage
        {
            get
            {
                return (ImageSource)GetValue(PanoramaImageProperty);
            }
            set
            {
                SetValue(PanoramaImageProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for PanoramaImage.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PanoramaImageProperty =
            DependencyProperty.Register("PanoramaImage",
                typeof(ImageSource),
                typeof(PanoramaViewer),
                new PropertyMetadata(
                    null,
                    OnPanoramaImageChanged));

        internal static void OnPanoramaImageChanged(Object sender, DependencyPropertyChangedEventArgs e)
        {
            PanoramaViewer Viewer = (PanoramaViewer)sender;
            ImageBrush PanoramaBrush = new ImageBrush(Viewer.PanoramaImage);
            Viewer.PanoramaGeometry.BackMaterial = new DiffuseMaterial(PanoramaBrush);
        }

        #region Rotation

        private static readonly Vector3D AxisX = new Vector3D(1, 0, 0);
        private static readonly Vector3D AxisY = new Vector3D(0, 1, 0);
        private static readonly Vector3D AxisZ = new Vector3D(0, 0, 1);

        public static readonly DependencyProperty RotationXProperty =
            DependencyProperty.Register("RotationX",
                typeof(double),
                typeof(PanoramaViewer),
                new UIPropertyMetadata(0.0, (d, args) => ((PanoramaViewer)d).UpdateRotation()));

        public static readonly DependencyProperty RotationYProperty =
            DependencyProperty.Register("RotationY",
                typeof(double),
                typeof(PanoramaViewer),
                new UIPropertyMetadata(0.0, (d, args) => ((PanoramaViewer)d).UpdateRotation()));

        public static readonly DependencyProperty RotationZProperty =
            DependencyProperty.Register("RotationZ",
                typeof(double),
                typeof(PanoramaViewer),
                new UIPropertyMetadata(0.0, (d, args) => ((PanoramaViewer)d).UpdateRotation()));


        public double RotationX
        {
            get
            {
                return (double)GetValue(RotationXProperty);
            }
            set
            {
                SetValue(RotationXProperty, value);
            }
        }

        public double RotationY
        {
            get
            {
                return (double)GetValue(RotationYProperty);
            }
            set
            {
                SetValue(RotationYProperty, value);
            }
        }

        public double RotationZ
        {
            get
            {
                return (double)GetValue(RotationZProperty);
            }
            set
            {
                SetValue(RotationZProperty, value);
            }
        }

        private void UpdateRotation()
        {
            Quaternion qx = new Quaternion(AxisX, RotationX);
            Quaternion qy = new Quaternion(AxisY, RotationY);
            Quaternion qz = new Quaternion(AxisZ, RotationZ);
            PanoramaRotation.Quaternion = qx * qy * qz;
        }

        QuaternionRotation3D PanoramaRotation
        {
            get;
            set;
        }

        #endregion

        public PanoramaViewer()
        {
            InitializeViewer();
        }

        public ModelVisual3D PanoramaObject
        {
            get;
            set;
        }

        public GeometryModel3D PanoramaGeometry
        {
            get;
            set;
        }

        public void InitializeViewer()
        {
            ///////////////////////////////////////////
            // Camera Initialize
            ///////////////////////////////////////////
            PerspectiveCamera PanoramaCamera = new PerspectiveCamera();
            PanoramaCamera.Position = new Point3D(0, -0.0, 0);
            PanoramaCamera.UpDirection = new Vector3D(0, 1, 0);
            PanoramaCamera.LookDirection = new Vector3D(0, 0, 1);
            PanoramaCamera.FieldOfView = 80;
            Camera = PanoramaCamera;

            FieldOfView = 80;

            ///////////////////////////////////////////
            // Light Initialize
            ///////////////////////////////////////////
            ModelVisual3D LightModel = new ModelVisual3D();
            LightModel.Content = new DirectionalLight(Colors.White, new Vector3D(0, 0, 1));
            Children.Add(LightModel);

            ///////////////////////////////////////////
            // Panorama Object Initialize
            ///////////////////////////////////////////

            PanoramaObject = new ModelVisual3D();
            PanoramaGeometry = new GeometryModel3D();
            PanoramaGeometry.Geometry = CreateGeometry();
            PanoramaObject.Content = PanoramaGeometry;

            RotateTransform3D RotateTransform = new RotateTransform3D();

            double x = 1.0;
            ScaleTransform3D ScaleTransform = new ScaleTransform3D()
            {
                ScaleX = x * 1, ScaleY = x * 1.65, ScaleZ = x * 1
            };

            Transform3DGroup Group = new Transform3DGroup();
            PanoramaRotation = new QuaternionRotation3D();
            Group.Children.Add(ScaleTransform);
            Group.Children.Add(RotateTransform);

            RotateTransform.Rotation = PanoramaRotation;
            PanoramaObject.Transform = Group;

            Children.Add(PanoramaObject);
        }

        private Geometry3D CreateGeometry()
        {
            int tDiv = 64;
            int yDiv = 64;
            double maxTheta = 360.0 / 180.0 * Math.PI;
            double minY = -1.0;
            double maxY = 1.0;

            double dt = maxTheta / tDiv;
            double dy = (maxY - minY) / yDiv;

            MeshGeometry3D mesh = new MeshGeometry3D();

            for (int yi = 0; yi <= yDiv; yi++)
            {
                double y = minY + yi * dy;

                for (int ti = 0; ti <= tDiv; ti++)
                {
                    double t = ti * dt;

                    mesh.Positions.Add(GetPosition(t, y));
                    mesh.Normals.Add(GetNormal(t, y));
                    mesh.TextureCoordinates.Add(GetTextureCoordinate(t, y));
                }
            }

            for (int yi = 0; yi < yDiv; yi++)
            {
                for (int ti = 0; ti < tDiv; ti++)
                {
                    int x0 = ti;
                    int x1 = ti + 1;
                    int y0 = yi * (tDiv + 1);
                    int y1 = (yi + 1) * (tDiv + 1);

                    mesh.TriangleIndices.Add(x0 + y0);
                    mesh.TriangleIndices.Add(x0 + y1);
                    mesh.TriangleIndices.Add(x1 + y0);

                    mesh.TriangleIndices.Add(x1 + y0);
                    mesh.TriangleIndices.Add(x0 + y1);
                    mesh.TriangleIndices.Add(x1 + y1);
                }
            }

            mesh.Freeze();
            return mesh;
        }

        internal Point3D GetPosition(double t, double y)
        {
            double r = Math.Sqrt(1 - y * y);
            double x = r * Math.Cos(t);
            double z = r * Math.Sin(t);

            return new Point3D(x, y, z);
        }

        private Vector3D GetNormal(double t, double y)
        {
            return (Vector3D)GetPosition(t, y);
        }

        private Point GetTextureCoordinate(double t, double y)
        {
            Matrix TYtoUV = new Matrix();
            TYtoUV.Scale(1 / (2 * Math.PI), -0.5);

            Point p = new Point(t, y);
            p = p * TYtoUV;

            return p;
        }
    }
}
